/*******************************************************************************
 * @file
 * @brief   虚拟串口
 *******************************************************************************/
#include "bsp.h"
#include "bsp_api.h"
#include "os_obj.h"
#include "dataproc.h"
#include "com_obj.h"
#include "bsp_cpu_it.h"
#include "str_ext.h"
#include "stm32f1xx_ll_usart.h"

/*******************************************************************************
 * @brief   基本接口
 *******************************************************************************/

static void com_hal_init(uint8_t comMode, uint32_t ulBaudRate, com_parity_type eParity);
static void com_rx_enable(uint8_t xEnable);
static void com_tx_enable(uint8_t xEnable);
static void com_timer_handle(void);
static void com_fun_init(uint8_t comMode);
static void com_hal_send(uint8_t dat);
static int16_t com_hal_read(void);

/*******************************************************************************
 * @brief   本地变量
 *******************************************************************************/
static uint8_t b_dir_ctr_req;

/*******************************************************************************
 * @brief   虚拟接口
 *******************************************************************************/
#ifndef ComObj

USART_TypeDef *UartSim;
#define com_dir_RxEnable()
#define com_dir_TxEnable()

/*******************************************************************************
 * @brief   接口功能
 *******************************************************************************/
#define COM_RX_EN 1
#define COM_RX_CALL_BACK_EN 0
#define MB_RS_485_MODE_EN 0
#define MB_RS_485_HALDIR_EN 0

#define comPORT UartSim
#define comIsr COM_UART_Sim_IRQHandler
#define COM_IRQn USART1_IRQn
#define ComObj com.obj01

static uint8_t com_tx_buff[2];
static uint8_t com_rx_buff[2];

/*******************************************************************************
 * @brief   FIFO 尺寸
 *******************************************************************************/
#define COM_PORT_FIFO_SIZE 1

/*******************************************************************************
 * @brief   PIN CFG.
 *
 * @ Pass/ Fail criteria: none
 *******************************************************************************/
static void com_pin_cfg(void)
{
}

/*******************************************************************************
 * @brief   	com port init .
 *
 * @ Pass/ Fail criteria: none
 *******************************************************************************/
void com_sim_Init(uint8_t comMode, uint32_t ulBaudRate, com_parity_type eParity)
{
	com_hal_init(comMode, ulBaudRate, eParity);
}

#endif

/*******************************************************************************
 * @brief   串口底层操作
 *******************************************************************************/
#define UART_SEND_DAT(dat) comPORT->DR = dat
#define UART_RCV_DAT() comPORT->DR
#define UART_RX_NE_ST() ((comPORT->SR & USART_SR_RXNE) != 0)							  // rx not empty
#define UART_TX_NF_ST() (0 != (comPORT->SR & USART_SR_TXE))								  // tx not full
#define UART_RX_INT_EN() comPORT->CR1 |= (USART_CR1_UE | USART_CR1_RE | USART_CR1_RXNEIE) // 开启RX中断
#define UART_RX_INT_DIS() comPORT->CR1 &= ~(USART_CR1_RXNEIE | USART_CR1_RE)
#define UART_RX_ERR_ST() (comPORT->SR & (USART_SR_PE | USART_SR_FE))
#define UART_RX_ERR_CLR() // 先读 sts 再读 dt自动清除
#define UART_TX_INT_EN() comPORT->CR1 |= (USART_CR1_UE | USART_CR1_TE | USART_CR1_TXEIE)
#define UART_TX_INT_DIS() comPORT->CR1 &= ~(USART_CR1_TXEIE)
#define UART_TX_TC_ST() ((comPORT->SR & USART_SR_TC) != 0)
#define UART_TX_EN() comPORT->CR1 |= (USART_CR1_UE | USART_CR1_TE)

static void UART_RX_DAT_CLR()
{
	volatile uint8_t u8Temp;
	while (UART_RX_NE_ST())
	{
		u8Temp = UART_RCV_DAT();
	}
}

com_api_make(comapi, ComObj);

/*****************************************************************************
 * @brief   com object init.
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_fun_init(uint8_t comMode)
{
	ComObj.b_auto_send = 1;
	ComObj.St.bits.run_en = 1;
	ComObj.master_wait_time_sv = 200;
	ComObj.rcv_end_keep_sv = 3;
	ComObj.rx_buff = com_rx_buff;
	ComObj.tx_buff = com_tx_buff;
	ComObj.rx_buff_size = sizeof(com_rx_buff);
	ComObj.tx_buff_size = sizeof(com_tx_buff);

	ComObj.fun = com_api_name(comapi);

	ComObj.b_master_mode = comMode;
}

/*****************************************************************************
 * @brief   	com modle init .
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_hal_init(uint8_t comMode, uint32_t ulBaudRate, com_parity_type eParity)
{
	uint32_t us10TimerSv;
	uint32_t ClocksPer2ms;
	LL_USART_InitTypeDef cfg;

	com_pin_cfg();

	set_u8(&ComObj, 0, sizeof(ComObj));

	cfg.BaudRate = ulBaudRate;
	cfg.TransferDirection = LL_USART_DIRECTION_TX_RX;
	cfg.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
	cfg.OverSampling = LL_USART_OVERSAMPLING_16;

	if (eParity == COM_PAR_ODD)
	{
		cfg.Parity = LL_USART_PARITY_ODD;
		cfg.StopBits = LL_USART_STOPBITS_1;
		cfg.DataWidth = LL_USART_DATAWIDTH_9B;
	}
	else if (eParity == COM_PAR_EVEN)
	{
		cfg.Parity = LL_USART_PARITY_EVEN;
		cfg.StopBits = LL_USART_STOPBITS_1;
		cfg.DataWidth = LL_USART_DATAWIDTH_9B;
	}
	else if (eParity == COM_PAR_NONE_2S)
	{
		cfg.Parity = LL_USART_PARITY_NONE;
		cfg.StopBits = LL_USART_STOPBITS_2;
		cfg.DataWidth = LL_USART_DATAWIDTH_8B;
	}
	else
	{
		cfg.Parity = LL_USART_PARITY_NONE;
		cfg.StopBits = LL_USART_STOPBITS_1;
		cfg.DataWidth = LL_USART_DATAWIDTH_8B;
	}

	// 11*3.5*(1E6)/ba/50 = 77*1E4/ba
	if (ulBaudRate > 19200)
	{
		ClocksPer2ms = 1800 * ulBaudRate / 1000000 + 1;

		if (ClocksPer2ms < 41)
			ClocksPer2ms = 41;
		else if (ClocksPer2ms > 255)
			ClocksPer2ms = 255;
		// TOI 时间设置
	}
	else
	{
		// Time-out Register ,TX-DLY=0, TX-TIMEOUT=40
		// TOI 时间设置
	}

	// 报文通知延时
	us10TimerSv = com_timer_cal(ulBaudRate, 11 * 5, 300, 20000);
	ComObj.rcv_end_time_sv = us10TimerSv / 100;

	if (comMode > 0)
	{
		ComObj.rcv_end_time_sv++;
	}

	LL_USART_Init(comPORT, &cfg);

	LL_USART_Enable(comPORT);

	nvic_irq_set(COM_IRQn, 0x06, 1);

	com_fun_init(comMode);

	com_rx_enable(ENABLE);
}

/*****************************************************************************
 * @brief   com end timer.
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_dir_handle()
{
	if (b_dir_ctr_req)
	{
#if ((MB_RS_485_MODE_EN > 0) && (MB_RS_485_HALDIR_EN == 0))
		// RS485模式且软件控制方向时，发送结束切换为接收模式
		if (UART_TX_TC_ST())
		{
			b_dir_ctr_req = 0;
			ComObj.snd_st = COM_TX_IDLE; // 发送结束
			com_tx_enable(DISABLE);
			if (ComObj.b_master_mode) // 主机进入接收等待状态
			{
				ComObj.rcv_st = COM_RX_WAIT;
				com_rx_enable(ENABLE);
			}
		}
#endif
	}
}

/*****************************************************************************
 * @brief   com end timer.
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_timer_handle(void)
{
	com_dir_handle();

	com_timer_run(&ComObj);
}

/*****************************************************************************
 * @brief   循环模式中断处理
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_isr_mode(void)
{
	volatile uint32_t IntSt;

	IntSt = comPORT->SR;
	ComObj.master_wait_time_cnt = 0;

#if COM_RX_EN > 0
	if (IntSt & USART_SR_RXNE)
	{
		uint16_t ReadCnt;

		// FIFO 中断
		ComObj.St.bits.port_st = 1;

		// 接收状态
		ComObj.rcv_st = COM_RX_RCV; // 指示正在接收数据
		ComObj.b_timer_on = 1;
		ComObj.rcv_end_time_cnt = 0;

		// 读数据
		ReadCnt = COM_PORT_FIFO_SIZE; // FIFO read
		while (ReadCnt--)
		{
			uint8_t datr;
			datr = UART_RCV_DAT();

			com_rx_rcv(&ComObj, datr);

#if COM_RX_CALL_BACK_EN > 0
			if (ComObj.rx_call_back)
			{
				ComObj.rx_call_back(datr);
			}
#endif
		}
	}
	else if (IntSt & USART_SR_TXE)
#else
	if (IntSt & USART_SR_TXE)
#endif
	// 发送缓冲空触发
	{
		if (ComObj.index_tx_send == ComObj.index_tx_fill)
		{
			// 发送已结束 //关闭中断
			com_tx_enable(0);
			if (ComObj.b_master_mode)
			{
				ComObj.rcv_st = COM_RX_WAIT;
			}
		}
		else
		{
			// 发送空中断
			while (UART_TX_NF_ST()) // 缓冲未满
			{
				if (ComObj.index_tx_send != ComObj.index_tx_fill) // 写缓冲区结束
				{
					// 继续发送
					UART_SEND_DAT(ComObj.tx_buff[ComObj.index_tx_send++]);

					if (ComObj.index_tx_send >= ComObj.tx_buff_size)
					{
						ComObj.index_tx_send = 0;
					}
				}
				else
				{
					// 发送结束
					break;
				}
			}
		}
	}
	else
	{
		// 未知错误，应重新初始化串口设备
		//  ComObj.St.bits.err_hal = 1;
		//  nvic_irq_set(COM_IRQn,0x0F,0);
	}
}

/*****************************************************************************
 * @brief   中断入口
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
void comIsr(void)
{
	com_isr_mode();
}

/*****************************************************************************
 * @brief   Uart Rx En or Dis.
 *
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_rx_enable(uint8_t xRxEnable)
{
	volatile uint8_t u8Temp;

	if (xRxEnable)
	{
#if COM_RX_EN > 0
		// 清空接收缓冲
		UART_RX_DAT_CLR();

		// 清除帧错误和奇偶校验错误
		UART_RX_ERR_CLR();

		// 开始接收
		UART_RX_INT_EN();
		com_dir_RxEnable();
#else
		UART_RX_INT_DIS();
#endif
	}
	else
	{
		UART_RX_INT_DIS();
	}
}

/*****************************************************************************
 * @brief   Uart Tx En or Dis.
 * @ Pass/ Fail criteria: none
 *****************************************************************************/
static void com_tx_enable(uint8_t xEnable)
{
	if (xEnable)
	{
		// 准备发送
		com_dir_TxEnable();

		ComObj.snd_st = COM_TX_XMIT;

		// Interrupt Enable Register 打开发送空中断
		UART_TX_INT_EN();
	}
	else
	{
		UART_TX_INT_DIS();
		com_dir_RxEnable();
		ComObj.snd_st = COM_TX_IDLE;
	}
}

static void com_hal_send(uint8_t dat)
{
	UART_TX_EN();

	UART_SEND_DAT(dat);

	while (!UART_TX_NF_ST())
	{
	}
}

static int16_t com_hal_read(void)
{
	int16_t dat;

	if (comPORT->SR & USART_SR_TXE)
	{
		dat = UART_RCV_DAT();
	}
	else
	{
		dat = -1;
	}
	return dat;
}
